﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Ndns.Packet.ResourceRecords
{
    public sealed class DnsResourceRecordSOA : DnsResourceRecord
    {
        /// <summary>
        /// 表示应答中的CNAME记录。
        /// </summary>
        public DnsHostName PrimaryNameServer { get; set; }

        /// <summary>
        /// Responsible authority's mailbox.
        /// </summary>
        public DnsHostName MailBox { get; set; }

        public uint SerialNumber { get; set; }

        public uint RefreshInterval { get; set; }

        public uint RetrialInterval { get; set; }

        public uint ExpireLimit { get; set; }

        public uint MinimumTTL { get; set; }

        public override DnsRecordType Type { get => DnsRecordType.SOA; set => throw new NotSupportedException(); }

        /// <summary>
        /// 根据相关信息构造DnsResourceRecordSOA实例。
        /// </summary>
        /// <param name="hostName">DNS应答的主机名信息。</param>
        /// <param name="c">DNS应答的类别。</param>
        /// <param name="ttl">DNS应答的超时时间。</param>
        public DnsResourceRecordSOA(DnsHostName hostName, DnsClass c, uint ttl,
            DnsHostName primaryNameAnswer, DnsHostName mailBox,
            uint serialNumber, uint refreshInterval, uint retrialInterval,
            uint expireLimit, uint minimumTTL)
            : base(hostName, c, ttl)
        {
            this.PrimaryNameServer = primaryNameAnswer ?? throw new ArgumentNullException(nameof(primaryNameAnswer));
            this.MailBox = mailBox ?? throw new ArgumentNullException(nameof(mailBox));
            this.SerialNumber = serialNumber;
            this.RefreshInterval = refreshInterval;
            this.RetrialInterval = retrialInterval;
            this.ExpireLimit = expireLimit;
            this.MinimumTTL = minimumTTL;
        }


        /// <summary>
        /// 从包含数据包的流中以支持指针的方式构造实例。
        /// </summary>
        /// <param name="packetStream">数据包流，Position应该指向要解析的应答的开始。</param>
        /// <param name="offset">数据包的开始位置在流中的偏移量。</param>
        /// <returns>解析得到的实例。</returns>
        public static new DnsResourceRecordSOA FromPacket(Stream packetStream, long packetStartIndex)
        {
            var hostName = DnsHostName.FromPacket(packetStream, packetStartIndex);
            var vType = packetStream.ReadNetworkUInt16();
            var vClass = packetStream.ReadNetworkUInt16();
            var vTTL = packetStream.ReadNetworkUInt32();
            int len = packetStream.ReadNetworkUInt16();
            PacketEndOfStreamException.CheckValue(vType, vClass, len);
            PacketEndOfStreamException.CheckValue(vTTL);

            if ((DnsRecordType)vType != DnsRecordType.SOA)
                throw new ArgumentException();

            long pos = packetStream.Position;

            var vPrimaryNameServer = DnsHostName.FromPacket(packetStream, packetStartIndex);
            var vMailBox = DnsHostName.FromPacket(packetStream, packetStartIndex);
            var vSerialNumber = packetStream.ReadNetworkUInt32();
            var vRefreshInterval = packetStream.ReadNetworkUInt32();
            var vRetrialInterval = packetStream.ReadNetworkUInt32();
            var vExpireLimit = packetStream.ReadNetworkUInt32();
            var vMinimumTTL = packetStream.ReadNetworkUInt32();
            PacketEndOfStreamException.CheckValue(vSerialNumber, vRefreshInterval, vRetrialInterval, vExpireLimit, vMinimumTTL);


            packetStream.Seek(pos + len, SeekOrigin.Begin);
            return new DnsResourceRecordSOA(hostName, (DnsClass)vClass, (uint)vTTL,
                vPrimaryNameServer, vMailBox,
                (uint)vSerialNumber, (uint)vRefreshInterval, (uint)vRetrialInterval, (uint)vExpireLimit, (uint)vMinimumTTL);
        }

        public override DnsResourceRecord Clone()
        {
            return new DnsResourceRecordSOA(HostName, Class, TTL, PrimaryNameServer, MailBox, SerialNumber, RefreshInterval, RetrialInterval, ExpireLimit, MinimumTTL);
        }

        public override byte[] GetData()
        {
            using (var ms = new MemoryStream())
            {
                this.CopyDataTo(ms);

                return ms.ToArray();
            }
        }

        protected override int CopyDataTo(Stream s)
        {
            int count;
            count = PrimaryNameServer.CopyTo(s);
            count += MailBox.CopyTo(s);
            s.WriteNetworkUInt32(SerialNumber);
            s.WriteNetworkUInt32(RefreshInterval);
            s.WriteNetworkUInt32(RetrialInterval);
            s.WriteNetworkUInt32(ExpireLimit);
            s.WriteNetworkUInt32(MinimumTTL);
            count += 20;
            return count;
        }

        protected override int CopyCompressedDataTo(Stream s, Dictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            int count;
            count = PrimaryNameServer.CopyCompressedTo(s, symbols, ref offset);
            count += MailBox.CopyCompressedTo(s, symbols, ref offset);
            s.WriteNetworkUInt32(SerialNumber);
            s.WriteNetworkUInt32(RefreshInterval);
            s.WriteNetworkUInt32(RetrialInterval);
            s.WriteNetworkUInt32(ExpireLimit);
            s.WriteNetworkUInt32(MinimumTTL);
            count += 20;
            offset += 20;
            return count;
        }

        protected override int GetDataHashCode()
        {
            int total = 17;
            total = total * 37 + PrimaryNameServer.GetHashCode();
            total = total * 37 + MailBox.GetHashCode();
            total = total * 37 + SerialNumber.GetHashCode();
            total = total * 37 + RefreshInterval.GetHashCode();
            total = total * 37 + RetrialInterval.GetHashCode();
            total = total * 37 + ExpireLimit.GetHashCode();
            total = total * 37 + MinimumTTL.GetHashCode();
            return total;
        }

        protected override bool TestDataEqualsTo(DnsResourceRecord other)
        {
            var record = other as DnsResourceRecordSOA;
            if (record == null)
                return false;
            if (SerialNumber != record.SerialNumber)
                return false;
            if (RefreshInterval != record.RefreshInterval)
                return false;
            if (RetrialInterval != record.RetrialInterval)
                return false;
            if (ExpireLimit != record.ExpireLimit)
                return false;
            if (MinimumTTL != record.MinimumTTL)
                return false;
            if (MailBox != record.MailBox)
                return false;
            return PrimaryNameServer == record.PrimaryNameServer;
        }
    }
}
